!--------------------------------------------------------------------------------------------------!
! Copyright (C) by the DBCSR developers group - All rights reserved                                !
! This file is part of the DBCSR library.                                                          !
!                                                                                                  !
! For information on the license, see the LICENSE file.                                            !
! For further information please visit https://dbcsr.cp2k.org                                      !
! SPDX-License-Identifier: GPL-2.0+                                                                !
!--------------------------------------------------------------------------------------------------!

MODULE dbcsr_io
   !! DBCSR input/output
   USE dbcsr_array_types, ONLY: array_data
   USE dbcsr_data_methods, ONLY: dbcsr_data_clear_pointer, &
                                 dbcsr_data_get_size_referenced, &
                                 dbcsr_data_init, &
                                 dbcsr_data_new, &
                                 dbcsr_get_data
   USE dbcsr_dist_methods, ONLY: dbcsr_distribution_mp
   USE dbcsr_kinds, ONLY: default_string_length, &
                          dp, &
                          real_4, &
                          real_4_size, &
                          real_8, &
                          real_8_size, &
                          sp
   USE dbcsr_machine, ONLY: default_output_unit
   USE dbcsr_methods, ONLY: &
      dbcsr_data_area, dbcsr_distribution, dbcsr_get_data_size, dbcsr_get_data_type, &
      dbcsr_get_matrix_type, dbcsr_get_num_blocks, dbcsr_name, dbcsr_nblkcols_total, &
      dbcsr_nblkrows_total, dbcsr_valid_index
   USE dbcsr_mp_methods, ONLY: dbcsr_mp_group, &
                               dbcsr_mp_pgrid
   USE dbcsr_mp_operations, ONLY: dbcsr_mp_type_from_anytype
   USE dbcsr_mpiwrap, ONLY: &
      file_amode_create, file_amode_rdonly, file_amode_wronly, file_offset, mp_allgather, &
      mp_environ, mp_file_close, mp_file_get_size, mp_file_open, mp_file_read_at_all, &
      mp_file_write_at, mp_file_write_at_all, mp_sum, mp_type_descriptor_type, mp_type_size, &
      mpi_character_size, mpi_integer_size, mp_comm_type, mp_file_type
   USE dbcsr_transformations, ONLY: dbcsr_datablock_redistribute
   USE dbcsr_types, ONLY: dbcsr_data_obj, &
                          dbcsr_distribution_obj, &
                          dbcsr_mp_obj, &
                          dbcsr_type, &
                          dbcsr_type_complex_4, &
                          dbcsr_type_complex_8, &
                          dbcsr_type_real_4, &
                          dbcsr_type_real_8
   USE dbcsr_work_operations, ONLY: dbcsr_create
#include "base/dbcsr_base_uses.f90"

   IMPLICIT NONE

   PRIVATE

   CHARACTER(len=*), PARAMETER, PRIVATE :: moduleN = 'dbcsr_io'

   ! Main
   PUBLIC :: dbcsr_print
   PUBLIC :: dbcsr_print_block_sum
   ! Low-level printing
! PUBLIC :: dbcsr_printmat, dbcsr_print2dmat
   ! Utility printing
   PUBLIC :: dbcsr_binary_write
   PUBLIC :: dbcsr_binary_read

   LOGICAL, PARAMETER :: bcsr_debug = .TRUE.
   LOGICAL, PARAMETER :: bcsr_info = .FALSE.
   LOGICAL, PARAMETER :: bcsr_verbose = .FALSE.

   INTERFACE dbcsr_printmat
      MODULE PROCEDURE printmat_s, printmat_d, printmat_c, printmat_z
   END INTERFACE

CONTAINS

   SUBROUTINE dbcsr_print(matrix, nodata, matlab_format, variable_name, unit_nr)
      !! Prints a BCSR matrix (block-style, not full)

      TYPE(dbcsr_type), INTENT(IN)                       :: matrix
         !! matrix
      LOGICAL, INTENT(IN), OPTIONAL                      :: nodata, matlab_format
         !! don't print actual data
      CHARACTER(LEN=*), INTENT(IN), OPTIONAL             :: variable_name
      INTEGER, INTENT(IN), OPTIONAL                      :: unit_nr

      CHARACTER(len=*), PARAMETER :: routineN = 'dbcsr_print', routineP = moduleN//':'//routineN

      COMPLEX(KIND=real_4), DIMENSION(:), POINTER        :: c_sp
      COMPLEX(KIND=real_8), DIMENSION(:), POINTER        :: c_dp
      INTEGER                                            :: ablk_p, bc, blk, blk_p, br, ebr, fblk, &
                                                            handle, ibr, iunit, lblk, m, mn, n, &
                                                            sblk
      INTEGER, DIMENSION(:), POINTER                     :: col_blk_offset, col_blk_size, &
                                                            local_cols, local_rows, &
                                                            row_blk_offset, row_blk_size
      LOGICAL                                            :: my_matlab_format, tr, yesprint
      REAL(KIND=dp)                                      :: blk_cs
      REAL(KIND=real_4), DIMENSION(:), POINTER           :: r_sp
      REAL(KIND=real_8), DIMENSION(:), POINTER           :: r_dp

!   ---------------------------------------------------------------------------

      CALL timeset(routineN, handle)
      IF (.NOT. dbcsr_valid_index(matrix)) &
         DBCSR_WARN("Can not print invalid matrix.")

      iunit = default_output_unit
      IF (PRESENT(unit_nr)) iunit = unit_nr

      my_matlab_format = .FALSE.
      IF (PRESENT(matlab_format)) my_matlab_format = matlab_format
      yesprint = .TRUE.
      IF (PRESENT(nodata)) yesprint = .NOT. nodata
      WRITE (iunit, *) routineP//' Contents of matrix named ', matrix%name
      WRITE (iunit, *) routineP//' Flags ', matrix%symmetry, &
         matrix%negate_real, matrix%negate_imaginary, "type", &
         dbcsr_get_data_type(matrix), "serial", matrix%serial_number
      WRITE (iunit, '(1X,A,3(1X,I9,1X,A))') routineP, matrix%nblks, "blocks", &
         matrix%nze, "nzes,", dbcsr_get_data_size(matrix), "data els", &
         dbcsr_data_get_size_referenced(matrix%data_area), "used"
      WRITE (iunit, '(1X,A,I5,A,I5)') routineP//" Full size", &
         matrix%nfullrows_total, "x", matrix%nfullcols_total
      WRITE (iunit, '(1X,A,I5,A,I5)') routineP//" Blocked size", &
         matrix%nblkrows_total, "x", matrix%nblkcols_total
      SELECT CASE (matrix%data_type)
      CASE (dbcsr_type_real_8)
         CALL dbcsr_get_data(matrix%data_area, r_dp)
      CASE (dbcsr_type_real_4)
         CALL dbcsr_get_data(matrix%data_area, r_sp)
      CASE (dbcsr_type_complex_8)
         CALL dbcsr_get_data(matrix%data_area, c_dp)
      CASE (dbcsr_type_complex_4)
         CALL dbcsr_get_data(matrix%data_area, c_sp)
      END SELECT
      row_blk_size => array_data(matrix%row_blk_size)
      col_blk_size => array_data(matrix%col_blk_size)
      row_blk_offset => array_data(matrix%row_blk_offset)
      col_blk_offset => array_data(matrix%col_blk_offset)

      IF (matrix%nblks .GT. 0) THEN
         IF (matrix%list_indexing) THEN
            IF (SIZE(matrix%coo_l) .NE. 3*matrix%nblks) &
               DBCSR_ABORT("Wrong list")
            ebr = 1
            sblk = 3
         ELSE
            ebr = matrix%nblkrows_total
            sblk = 1
         END IF
         DO ibr = 1, ebr
            IF (matrix%list_indexing) THEN
               fblk = 1
               lblk = SIZE(matrix%coo_l)
            ELSE
               br = ibr
               fblk = matrix%row_p(br) + 1
               lblk = matrix%row_p(br + 1)
               m = row_blk_size(br)
            END IF
            DO blk = fblk, lblk, sblk
               IF (matrix%list_indexing) THEN
                  br = matrix%coo_l(blk)
                  bc = matrix%coo_l(blk + 1)
                  IF (matrix%local_indexing) THEN
                     local_rows => array_data(matrix%local_rows)
                     local_cols => array_data(matrix%local_cols)
                     br = local_rows(br)
                     bc = local_cols(bc)
                  END IF
                  m = row_blk_size(br)
                  ablk_p = matrix%coo_l(blk + 2)
               ELSE
                  bc = matrix%col_i(blk)
                  ablk_p = matrix%blk_p(blk)
               END IF
               n = col_blk_size(bc)
               mn = m*n
               blk_p = ABS(ablk_p)
               tr = ablk_p .LT. 0
               block_exists: IF (blk_p .NE. 0) THEN
                  IF (mn .GT. 0) THEN
                     SELECT CASE (matrix%data_type)
                     CASE (dbcsr_type_real_8)
                        blk_cs = REAL(DOT_PRODUCT(r_dp(blk_p:blk_p + mn - 1), &
                                                  r_dp(blk_p:blk_p + mn - 1)), KIND=dp)
                        !CALL &
                        !     dbcsr_printmat(r_dp(blk_p:blk_p+mn-1),m,n, tr=tr)
                     CASE (dbcsr_type_real_4)
                        blk_cs = REAL(DOT_PRODUCT(r_sp(blk_p:blk_p + mn - 1), &
                                                  r_sp(blk_p:blk_p + mn - 1)), KIND=dp)
                        !CALL &
                        !     dbcsr_printmat(r_sp(blk_p:blk_p+mn-1),m,n, tr=tr)
                     CASE (dbcsr_type_complex_8)
                        blk_cs = REAL(DOT_PRODUCT(c_dp(blk_p:blk_p + mn - 1), &
                                                  c_dp(blk_p:blk_p + mn - 1)), KIND=dp)
                        !CALL &
                        !     dbcsr_printmat(c_dp(blk_p:blk_p+mn-1),m,n, tr=tr)
                     CASE (dbcsr_type_complex_4)
                        blk_cs = REAL(DOT_PRODUCT(c_sp(blk_p:blk_p + mn - 1), &
                                                  c_sp(blk_p:blk_p + mn - 1)), KIND=dp)
                        !CALL &
                        !     dbcsr_printmat(c_sp(blk_p:blk_p+mn-1),m,n, tr=tr)
                     END SELECT
                  ELSE
                     blk_cs = 0.0_dp
                  END IF
                  !WRITE(iunit,*)routineP//' chksum for (',br,',',bc,') at',&
                  !     blk_p,'l',mn,'= ', blk_cs,'size',m,n
                  IF (.NOT. my_matlab_format) WRITE (iunit, '(A,I6,",",I6,A,I7,A,I6,I6,"=",I7,A,E12.3)') &
                     !" Checksum for (",br,bc,") at ",blk_p," size ",m,n,mn,&
                     " Checksum for (", br, bc, ") at ", ablk_p, " size ", m, n, mn, &
                     " checksum=", blk_cs
                  IF (yesprint .AND. blk_p .NE. 0) THEN
                     IF (mn .GT. 0) THEN
                        SELECT CASE (matrix%data_type)
                        CASE (dbcsr_type_real_8)
                           !WRITE(iunit,'(10(1X,F7.2))')r_dp(blk_p:blk_p+mn-1)
                           IF (my_matlab_format) THEN
                              CALL dbcsr_printmat_matlab_d(r_dp(blk_p:blk_p + mn - 1), m, n, &
                                                           row_blk_offset(br), col_blk_offset(bc), iunit, tr=tr, &
                                                           variable_name=variable_name)
                           ELSE
                              CALL dbcsr_printmat(r_dp(blk_p:blk_p + mn - 1), m, n, iunit=iunit, tr=tr)
                           END IF
                        CASE (dbcsr_type_real_4)
                           IF (my_matlab_format) THEN
                              CALL dbcsr_printmat_matlab_s(r_sp(blk_p:blk_p + mn - 1), m, n, &
                                                           row_blk_offset(br), col_blk_offset(bc), iunit, tr=tr, &
                                                           variable_name=variable_name)
                           ELSE
                              CALL dbcsr_printmat(r_sp(blk_p:blk_p + mn - 1), m, n, iunit=iunit, tr=tr)
                           END IF
                        CASE (dbcsr_type_complex_8)
                           IF (my_matlab_format) THEN
                              CALL dbcsr_printmat_matlab_z(c_dp(blk_p:blk_p + mn - 1), m, n, &
                                                           row_blk_offset(br), col_blk_offset(bc), iunit, tr=tr, &
                                                           variable_name=variable_name)
                           ELSE
                              CALL dbcsr_printmat(c_dp(blk_p:blk_p + mn - 1), m, n, iunit=iunit, tr=tr)
                           END IF
                        CASE (dbcsr_type_complex_4)
                           IF (my_matlab_format) THEN
                              CALL dbcsr_printmat_matlab_c(c_sp(blk_p:blk_p + mn - 1), m, n, &
                                                           row_blk_offset(br), col_blk_offset(bc), iunit, tr=tr, &
                                                           variable_name=variable_name)
                           ELSE
                              CALL dbcsr_printmat(c_sp(blk_p:blk_p + mn - 1), m, n, iunit=iunit, tr=tr)
                           END IF
                        END SELECT
                     END IF
                  END IF
               END IF block_exists
            END DO
         END DO
      END IF
      CALL timestop(handle)
   END SUBROUTINE dbcsr_print

   SUBROUTINE dbcsr_printmat_matlab_d(matrix, rows, cols, r_offset, c_offset, iunit, tr, variable_name)
      !! Prints the elements of a matrix.

      REAL(KIND=real_8), DIMENSION(:), INTENT(IN)        :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, r_offset, c_offset, iunit
         !! the logical (possibly detransposed) matrix size, not the stored size
         !! the logical (possibly detransposed) matrix size, not the stored size
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr
         !! specifies whether the elements are stored transposed
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: variable_name

      INTEGER                                            :: c, c_off, m, n, r, r_off
      LOGICAL                                            :: t

!   ---------------------------------------------------------------------------

      m = rows
      n = cols
      r_off = r_offset
      c_off = c_offset
      t = .FALSE.
      IF (PRESENT(tr)) THEN
         IF (tr) THEN
            t = .TRUE.
            m = cols
            n = rows
            r_off = c_offset
            c_off = r_offset
         END IF
      END IF

      DO c = 1, cols
      DO r = 1, rows
         IF (.NOT. t) THEN
            IF (PRESENT(variable_name)) THEN
               WRITE (iunit, '(A,I4,A,I4,A,E23.16,A)') &
                  variable_name//'(', r + r_offset - 1, ',', c + c_offset - 1, ')=', matrix(r + (c - 1)*rows), ';'
            ELSE
               WRITE (iunit, '(A,I4,A,I4,A,E23.16,A)') 'a(', r + r_offset - 1, ',', &
                  c + c_offset - 1, ')=', matrix(r + (c - 1)*rows), ';'
            END IF
         ELSE
            IF (PRESENT(variable_name)) THEN
               WRITE (iunit, '(A,I4,A,I4,A,E23.16,A)') &
                  variable_name//'(', r + r_offset - 1, ',', c + c_offset - 1, ')=', matrix((r - 1)*cols + c), ';'
            ELSE
               WRITE (iunit, '(A,I4,A,I4,A,E23.16,A)') 'a(', r + r_offset - 1, ',', &
                  c + c_offset - 1, ')=', matrix((r - 1)*cols + c), ';'
            END IF
         END IF
      END DO
      END DO
   END SUBROUTINE dbcsr_printmat_matlab_d

   SUBROUTINE dbcsr_printmat_matlab_s(matrix, rows, cols, r_offset, c_offset, iunit, tr, variable_name)
      REAL(KIND=real_4), DIMENSION(:), INTENT(IN)        :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, r_offset, c_offset, iunit
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: variable_name

      INTEGER                                            :: c, c_off, m, n, r, r_off
      LOGICAL                                            :: t

!   ---------------------------------------------------------------------------

      m = rows
      n = cols
      r_off = r_offset
      c_off = c_offset
      t = .FALSE.
      IF (PRESENT(tr)) THEN
         IF (tr) THEN
            t = .TRUE.
            m = cols
            n = rows
            r_off = c_offset
            c_off = r_offset
         END IF
      END IF

      DO c = 1, cols
      DO r = 1, rows
         IF (.NOT. t) THEN
            IF (PRESENT(variable_name)) THEN
               WRITE (iunit, '(A,I4,A,I4,A,E15.7,A)') &
                  variable_name//'(', r + r_offset - 1, ',', c + c_offset - 1, ')=', matrix(r + (c - 1)*rows), ';'
            ELSE
               WRITE (iunit, '(A,I4,A,I4,A,E15.7,A)') 'a(', r + r_offset - 1, ',', &
                  c + c_offset - 1, ')=', matrix(r + (c - 1)*rows), ';'
            END IF
         ELSE
            IF (PRESENT(variable_name)) THEN
               WRITE (iunit, '(A,I4,A,I4,A,E15.7,A)') &
                  variable_name//'(', r + r_offset - 1, ',', c + c_offset - 1, ')=', matrix((r - 1)*cols + c), ';'
            ELSE
               WRITE (iunit, '(A,I4,A,I4,A,E15.7,A)') 'a(', r + r_offset - 1, ',', &
                  c + c_offset - 1, ')=', matrix((r - 1)*cols + c), ';'
            END IF
         END IF
      END DO
      END DO
   END SUBROUTINE dbcsr_printmat_matlab_s

   SUBROUTINE dbcsr_printmat_matlab_z(matrix, rows, cols, r_offset, c_offset, iunit, tr, variable_name)
      COMPLEX(KIND=real_8), DIMENSION(:), INTENT(IN)     :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, r_offset, c_offset, iunit
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: variable_name

      INTEGER                                            :: c, c_off, m, n, r, r_off
      LOGICAL                                            :: t

!   ---------------------------------------------------------------------------

      m = rows
      n = cols
      r_off = r_offset
      c_off = c_offset
      t = .FALSE.
      IF (PRESENT(tr)) THEN
         IF (tr) THEN
            t = .TRUE.
            m = cols
            n = rows
            r_off = c_offset
            c_off = r_offset
         END IF
      END IF

      DO c = 1, cols
         DO r = 1, rows
            IF (.NOT. t) THEN
               IF (PRESENT(variable_name)) THEN
                  WRITE (iunit, '(A,I3,A,I3,A,E23.16,A,E23.16,A)') variable_name//'(', r + r_offset - 1, ',', &
                     c + c_offset - 1, ')=', &
                     REAL(matrix(r + (c - 1)*rows)), '+', AIMAG(matrix(r + (c - 1)*rows)), 'i;'
               ELSE
                  WRITE (iunit, '(A,I3,A,I3,A,E23.16,A,E23.16,A)') 'a(', r + r_offset - 1, ',', c + c_offset - 1, ')=', &
                     REAL(matrix(r + (c - 1)*rows)), '+', AIMAG(matrix(r + (c - 1)*rows)), 'i;'
               END IF
            ELSE
               IF (PRESENT(variable_name)) THEN
                  WRITE (iunit, '(A,I3,A,I3,A,E23.16,A,E23.16,A)') variable_name//'(', r + r_offset - 1, ',', &
                     c + c_offset - 1, ')=', &
                     REAL(matrix((r - 1)*cols + c)), '+', AIMAG(matrix((r - 1)*cols + c)), 'i;'
               ELSE
                  WRITE (iunit, '(A,I3,A,I3,A,E23.16,A,E23.16,A)') 'a(', r + r_offset - 1, ',', c + c_offset - 1, ')=', &
                     REAL(matrix((r - 1)*cols + c)), '+', AIMAG(matrix((r - 1)*cols + c)), 'i;'
               END IF
            END IF
         END DO
      END DO
   END SUBROUTINE dbcsr_printmat_matlab_z

   SUBROUTINE dbcsr_printmat_matlab_c(matrix, rows, cols, r_offset, c_offset, iunit, tr, variable_name)
      COMPLEX(KIND=real_4), DIMENSION(:), INTENT(IN)     :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, r_offset, c_offset, iunit
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr
      CHARACTER(len=*), INTENT(in), OPTIONAL             :: variable_name

      INTEGER                                            :: c, c_off, m, n, r, r_off
      LOGICAL                                            :: t

!   ---------------------------------------------------------------------------

      m = rows
      n = cols
      r_off = r_offset
      c_off = c_offset
      t = .FALSE.
      IF (PRESENT(tr)) THEN
         IF (tr) THEN
            t = .TRUE.
            m = cols
            n = rows
            r_off = c_offset
            c_off = r_offset
         END IF
      END IF

      DO c = 1, cols
         DO r = 1, rows
            IF (.NOT. t) THEN
               IF (PRESENT(variable_name)) THEN
                  WRITE (iunit, '(A,I3,A,I3,A,E15.7,A,E15.7,A)') variable_name//'(', r + r_offset - 1, ',', &
                     c + c_offset - 1, ')=', &
                     REAL(matrix(r + (c - 1)*rows)), '+', AIMAG(matrix(r + (c - 1)*rows)), 'i;'
               ELSE
                  WRITE (iunit, '(A,I3,A,I3,A,E15.7,A,E15.7,A)') 'a(', r + r_offset - 1, ',', c + c_offset - 1, ')=', &
                     REAL(matrix(r + (c - 1)*rows)), '+', AIMAG(matrix(r + (c - 1)*rows)), 'i;'
               END IF
            ELSE
               IF (PRESENT(variable_name)) THEN
                  WRITE (iunit, '(A,I3,A,I3,A,E15.7,A,E15.7,A)') variable_name//'(', r + r_offset - 1, ',', &
                     c + c_offset - 1, ')=', &
                     REAL(matrix((r - 1)*cols + c)), '+', AIMAG(matrix((r - 1)*cols + c)), 'i;'
               ELSE
                  WRITE (iunit, '(A,I3,A,I3,A,E15.7,A,E15.7,A)') 'a(', r + r_offset - 1, ',', c + c_offset - 1, ')=', &
                     REAL(matrix((r - 1)*cols + c)), '+', AIMAG(matrix((r - 1)*cols + c)), 'i;'
               END IF
            END IF
         END DO
      END DO
   END SUBROUTINE dbcsr_printmat_matlab_c

   SUBROUTINE printmat_s(matrix, rows, cols, iunit, title, tr)
      !! Prints the elements of a matrix.

      REAL(KIND=real_4), DIMENSION(:), INTENT(IN)        :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, iunit
         !! the logical (possibly detransposed) matrix size, not the stored size
         !! the logical (possibly detransposed) matrix size, not the stored size
      CHARACTER(*), INTENT(IN), OPTIONAL                 :: title
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr
         !! specifies whether the elements are stored transposed

      CHARACTER(30)                                      :: f
      INTEGER                                            :: m, n, r
      LOGICAL                                            :: t
      REAL(KIND=dp)                                      :: bit_bucket

!   ---------------------------------------------------------------------------

      m = rows
      n = cols
      t = .FALSE.
      IF (PRESENT(title)) WRITE (iunit, *) title
      IF (PRESENT(tr)) THEN
         IF (tr) THEN
            t = .TRUE.
            m = cols
            n = rows
         END IF
      END IF
      DO r = LBOUND(matrix, 1), UBOUND(matrix, 1)
         bit_bucket = matrix(r)
      END DO
      bit_bucket = 0.0_dp
      DO r = LBOUND(matrix, 1), UBOUND(matrix, 1)
         bit_bucket = bit_bucket + matrix(r)
      END DO
      IF (m .GT. 10000) m = 0
      IF (n .GT. 10000) n = 0
      IF (m*n .LT. 1 .OR. m*n .GT. SIZE(matrix)) RETURN
      WRITE (f, FMT="('(',I4,'(F9.4))')") cols
      DO r = 1, rows
         IF (.NOT. t) THEN
            WRITE (iunit, FMT=f) matrix(r:r + (cols - 1)*rows:rows)
         ELSE
            WRITE (iunit, FMT=f) matrix((r - 1)*cols + 1:r*cols)
         END IF
      END DO
   END SUBROUTINE printmat_s

   SUBROUTINE printmat_d(matrix, rows, cols, iunit, title, tr)
      REAL(KIND=real_8), DIMENSION(:), INTENT(IN)        :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, iunit
      CHARACTER(*), INTENT(IN), OPTIONAL                 :: title
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr

      IF (PRESENT(title)) THEN
         IF (PRESENT(tr)) THEN
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, title, tr)
         ELSE
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, title)
         END IF
      ELSE
         IF (PRESENT(tr)) THEN
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, tr=tr)
         ELSE
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit)
         END IF
      END IF
   END SUBROUTINE printmat_d

   SUBROUTINE printmat_c(matrix, rows, cols, iunit, title, tr)
      COMPLEX(KIND=real_4), DIMENSION(:), INTENT(IN)     :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, iunit
      CHARACTER(*), INTENT(IN), OPTIONAL                 :: title
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr

      IF (PRESENT(title)) THEN
         IF (PRESENT(tr)) THEN
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, title, tr)
         ELSE
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, title)
         END IF
      ELSE
         IF (PRESENT(tr)) THEN
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, tr=tr)
         ELSE
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit)
         END IF
      END IF
   END SUBROUTINE printmat_c

   SUBROUTINE printmat_z(matrix, rows, cols, iunit, title, tr)
      COMPLEX(KIND=real_8), DIMENSION(:), INTENT(IN)     :: matrix
      INTEGER, INTENT(IN)                                :: rows, cols, iunit
      CHARACTER(*), INTENT(IN), OPTIONAL                 :: title
      LOGICAL, INTENT(IN), OPTIONAL                      :: tr

      IF (PRESENT(title)) THEN
         IF (PRESENT(tr)) THEN
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, title, tr)
         ELSE
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, title)
         END IF
      ELSE
         IF (PRESENT(tr)) THEN
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit, tr=tr)
         ELSE
            CALL printmat_s(REAL(matrix, KIND=sp), rows, cols, iunit)
         END IF
      END IF
   END SUBROUTINE printmat_z

   SUBROUTINE dbcsr_binary_write(matrix, filepath)
      !! Writes a DBCSR matrix in a file
      !! file's header consists of 3 sub-headers:
      !! sub-header1 contains:
      !! 1 string: (of length version_len) the current version of this routine,
      !! 1 string: (of length default_string_length) matrix_name,
      !! 1 character: matrix_type,
      !! 4 integers: numnodes, data_type, nblkrows_total, nblkcols_total,
      !! 2 vectors:  row_blk_size (length = nblkrows_total),
      !! col_blk_size (length = nblkcols_total),
      !! sub-header2 contains:
      !! 2 integers: nblks, data_area_size,
      !! sub-header3 contains:
      !! 3 vectors:  row_p (length = nblkrows_total+1),
      !! col_i (length = nblks),
      !! blk_p (length = nblks);
      !! and the file's body contains the block data

      IMPLICIT NONE

      TYPE(dbcsr_type), INTENT(IN)           :: matrix
         !! DBCSR matrix
      CHARACTER(len=*), INTENT(IN)         :: filepath
         !! path to the file

      CHARACTER(LEN=*), PARAMETER :: routineN = 'dbcsr_binary_write'

      INTEGER                               :: nblkrows_total, nblkcols_total, &
                                               nblks, size_of_pgrid, &
                                               i, sendbuf, data_area_size, &
                                               data_type, type_size, &
                                               mynode, numnodes, &
                                               ginfo_size, linfo_size, handle
      INTEGER, DIMENSION(:), POINTER        :: row_p, col_i, blk_p, &
                                               row_blk_size, col_blk_size
      INTEGER, DIMENSION(:, :), POINTER      :: pgrid
      TYPE(mp_type_descriptor_type)         :: mp_type
      TYPE(dbcsr_mp_obj)                    :: mp_env
      TYPE(dbcsr_distribution_obj)          :: distribution
      TYPE(dbcsr_data_obj)                  :: data_area
      COMPLEX(sp), DIMENSION(:), POINTER      :: c_sp
      COMPLEX(dp), DIMENSION(:), POINTER      :: c_dp
      REAL(sp), DIMENSION(:), POINTER         :: r_sp
      REAL(dp), DIMENSION(:), POINTER         :: r_dp
      CHARACTER                             :: matrix_type
      CHARACTER(LEN=80)                     :: matrix_name_v_1_0
      CHARACTER(LEN=default_string_length)  :: matrix_name
      TYPE(mp_comm_type)                    :: mp_group
      TYPE(mp_file_type)                    :: thefile
      INTEGER, PARAMETER                    :: version_len = 10
      CHARACTER(LEN=version_len), PARAMETER :: version = "DBCSRv_1.0"
      INTEGER, ALLOCATABLE, DIMENSION(:) :: linfo_sizes, da_sizes
      INTEGER(kind=file_offset), ALLOCATABLE, DIMENSION(:) :: bdata_disps, bdata_offsets, &
                                                              subh2_disps, subh2_offsets, &
                                                              subh3_disps, subh3_offsets
      INTEGER(kind=file_offset), PARAMETER                 :: BOF = 0
      INTEGER, PARAMETER          :: char_count = version_len + default_string_length + 1 !version, matrix_name, matrix_type

      CALL timeset(routineN, handle)

      IF (default_string_length /= 80) &
         CALL dbcsr_warn(__LOCATION__, "Changing the default string length affects "// &
                         "the format of the written matrix. Version needs to be adjusted")

      nblkrows_total = dbcsr_nblkrows_total(matrix)
      nblkcols_total = dbcsr_nblkcols_total(matrix)
      distribution = dbcsr_distribution(matrix)
      matrix_name = dbcsr_name(matrix)
      data_area = dbcsr_data_area(matrix)
      matrix_type = dbcsr_get_matrix_type(matrix)
      data_type = dbcsr_get_data_type(matrix)
      mp_env = dbcsr_distribution_mp(distribution)
      mp_group = dbcsr_mp_group(mp_env)
      nblks = dbcsr_get_num_blocks(matrix)
      row_p => matrix%row_p
      col_i => matrix%col_i
      blk_p => matrix%blk_p
      row_blk_size => array_data(matrix%row_blk_size)
      col_blk_size => array_data(matrix%col_blk_size)
      pgrid => dbcsr_mp_pgrid(mp_env)
      size_of_pgrid = SIZE(pgrid)

      CALL mp_environ(numnodes, mynode, mp_group)

      ALLOCATE (linfo_sizes(numnodes), da_sizes(numnodes), &
                subh2_disps(numnodes), subh2_offsets(numnodes), &
                subh3_disps(numnodes), subh3_offsets(numnodes), &
                bdata_disps(numnodes), bdata_offsets(numnodes))
      subh2_disps(:) = (/((i - 1)*2, i=1, numnodes)/)
      subh3_disps = BOF
      bdata_disps = BOF
      linfo_sizes = BOF
      subh2_offsets = BOF
      subh3_offsets = BOF
      bdata_offsets = BOF
      da_sizes = BOF

      ginfo_size = char_count + 4 + nblkrows_total + nblkcols_total
      linfo_size = 1 + nblkrows_total + 2*nblks

      sendbuf = linfo_size
      CALL mp_allgather(sendbuf, linfo_sizes, mp_group)
      CALL cumsum_l(INT(linfo_sizes, kind=file_offset), subh3_disps)
      subh3_disps(:) = CSHIFT(subh3_disps, shift=-1) + ginfo_size + 2*numnodes
      subh3_disps(1) = ginfo_size + 2*numnodes

      data_area_size = dbcsr_data_get_size_referenced(matrix%data_area)
      sendbuf = data_area_size
      CALL mp_allgather(sendbuf, da_sizes, mp_group)
      CALL cumsum_l(INT(da_sizes, kind=file_offset), bdata_disps)
      bdata_disps(:) = CSHIFT(bdata_disps, shift=-1) + SUM(INT(linfo_sizes, KIND=file_offset)) + &
                       ginfo_size + numnodes*2
      bdata_disps(1) = SUM(INT(linfo_sizes, KIND=file_offset)) + ginfo_size + numnodes*2

      CALL mp_file_open(mp_group, thefile, filepath, file_amode_create + file_amode_wronly)

      IF (mynode .EQ. 0) THEN
         CALL mp_file_write_at(thefile, BOF, version)
         matrix_name_v_1_0 = matrix_name
         CALL mp_file_write_at(thefile, BOF + version_len*mpi_character_size, matrix_name_v_1_0)
         CALL mp_file_write_at(thefile, BOF + (version_len + default_string_length)*mpi_character_size, matrix_type)
         CALL mp_file_write_at(thefile, BOF + char_count*mpi_character_size, &
                               (/size_of_pgrid, data_type, &
                                 nblkrows_total, nblkcols_total, &
                                 row_blk_size, col_blk_size/))
      END IF
! write sub-header2
      subh2_disps(:) = subh2_disps(:) + ginfo_size
      subh2_offsets(:) = BOF + (subh2_disps - char_count)*mpi_integer_size + &
                         char_count*mpi_character_size
      CALL mp_file_write_at_all(thefile, subh2_offsets(mynode + 1), (/nblks, data_area_size/))
! write sub-header3
      subh3_offsets(:) = BOF + (subh3_disps - char_count)*mpi_integer_size + &
                         char_count*mpi_character_size
      CALL mp_file_write_at_all(thefile, subh3_offsets(mynode + 1), (/row_p, col_i, blk_p/))
! write block data
      mp_type = dbcsr_mp_type_from_anytype(data_area)
      CALL mp_type_size(mp_type, type_size)
      bdata_offsets(:) = BOF + (/((bdata_disps(i) - bdata_disps(1))*type_size, i=1, numnodes)/) + &
                         (bdata_disps(1) - char_count)*mpi_integer_size + &
                         char_count*mpi_character_size
      SELECT CASE (data_type)
      CASE (dbcsr_type_real_4)
         r_sp => data_area%d%r_sp
         CALL mp_file_write_at_all(thefile, bdata_offsets(mynode + 1), r_sp, msglen=data_area_size)
      CASE (dbcsr_type_real_8)
         r_dp => data_area%d%r_dp
         CALL mp_file_write_at_all(thefile, bdata_offsets(mynode + 1), r_dp, msglen=data_area_size)
      CASE (dbcsr_type_complex_4)
         c_sp => data_area%d%c_sp
         CALL mp_file_write_at_all(thefile, bdata_offsets(mynode + 1), c_sp, msglen=data_area_size)
      CASE (dbcsr_type_complex_8)
         c_dp => data_area%d%c_dp
         CALL mp_file_write_at_all(thefile, bdata_offsets(mynode + 1), c_dp, msglen=data_area_size)
      END SELECT
      CALL mp_file_close(thefile)

      DEALLOCATE (linfo_sizes, da_sizes)
      DEALLOCATE (subh2_disps, subh2_offsets, subh3_disps, subh3_offsets)
      DEALLOCATE (bdata_disps, bdata_offsets)

      CALL timestop(handle)

   CONTAINS
      SUBROUTINE cumsum_l(arr, cumsum)
         INTEGER(kind=file_offset), DIMENSION(:), &
            INTENT(IN)                                      :: arr
         INTEGER(kind=file_offset), DIMENSION(SIZE(arr)), &
            INTENT(OUT)                                     :: cumsum

         INTEGER                                            :: i

         cumsum(1) = arr(1)
         DO i = 2, SIZE(arr)
            cumsum(i) = cumsum(i - 1) + arr(i)
         END DO
      END SUBROUTINE cumsum_l
   END SUBROUTINE dbcsr_binary_write

   SUBROUTINE dbcsr_binary_read(filepath, distribution, matrix_new)
      !! Reads a DBCSR matrix from a file

      IMPLICIT NONE

      CHARACTER(len=*), INTENT(IN)                :: filepath
         !! path to the file
      TYPE(dbcsr_distribution_obj), INTENT(IN)     :: distribution
         !! row and column distribution
      TYPE(dbcsr_type), INTENT(INOUT)               :: matrix_new
         !! DBCSR matrix

      CHARACTER(LEN=*), PARAMETER :: routineN = 'dbcsr_binary_read'

      INTEGER                               :: nblkrows_total, nblkcols_total, &
                                               nblks, darea_size, data_type, type_size, &
                                               globalinfo_size, &
                                               size_of_pgrid, &
                                               i, j, &
                                               nblocks, &
                                               share_size, order, cur_blks, &
                                               job_count, start_index, end_index, &
                                               localinfo_length, blockdata_length, &
                                               worker_id, group_list_size, handle, linfo_length
      CHARACTER                             :: matrix_type
      CHARACTER(LEN=default_string_length)  :: matrix_name
      INTEGER, PARAMETER                    :: version_len = 10
      CHARACTER(LEN=version_len)            :: version
      CHARACTER(LEN=80)                     :: matrix_name_v_1_0
      CHARACTER(LEN=version_len), PARAMETER :: version_v_1_0 = "DBCSRv_1.0"
      TYPE(mp_comm_type)                    :: group_id
      TYPE(mp_file_type)                    :: thefile

      INTEGER, DIMENSION(:), POINTER        :: row_p, col_i, blk_p, &
                                               proc_nblks, proc_darea_sizes
      INTEGER, DIMENSION(4)                 :: values
      INTEGER, ALLOCATABLE, DIMENSION(:)    :: linfo_lens, bdata_lens
      INTEGER, ALLOCATABLE, DIMENSION(:), TARGET     :: ginfo_vec, linfo_vec, &
                                                        rowp, coli, blkp
      INTEGER, ALLOCATABLE, DIMENSION(:, :), TARGET   :: val_data
      INTEGER, DIMENSION(:), POINTER, CONTIGUOUS     :: row_blk_size, col_blk_size
      TYPE(dbcsr_mp_obj)                             :: mp_env
      TYPE(dbcsr_data_obj)                           :: dblk
      REAL(sp)                                       :: rsp_dummy(1)
      REAL(dp)                                       :: rdp_dummy(1)
      COMPLEX(sp)                                    :: csp_dummy(1)
      COMPLEX(dp)                                    :: cdp_dummy(1)
      REAL(sp), ALLOCATABLE, DIMENSION(:), TARGET     :: rsp
      REAL(dp), ALLOCATABLE, DIMENSION(:), TARGET     :: rdp
      COMPLEX(sp), ALLOCATABLE, DIMENSION(:), TARGET  :: csp
      COMPLEX(dp), ALLOCATABLE, DIMENSION(:), TARGET  :: cdp
      INTEGER(kind=file_offset), ALLOCATABLE, DIMENSION(:)   :: subh2_offsets, &
                                                                subh3_disps, subh3_offsets, &
                                                                bdata_disps, bdata_offsets
      INTEGER(kind=file_offset), PARAMETER    :: BOF = 0
      INTEGER(kind=file_offset)               :: offset, subh2_start, subh3_start, bdata_start, file_size, &
                                                 localinfo_offset, blockdata_offset, sum_nblks, subh3_length, data_area_size
      INTEGER, PARAMETER                      :: char_count = 1 + version_len + default_string_length

      CALL timeset(routineN, handle)

      mp_env = dbcsr_distribution_mp(distribution)
      group_id = dbcsr_mp_group(mp_env)

      CALL mp_environ(group_list_size, worker_id, group_id)
      CALL mp_file_open(group_id, thefile, filepath, file_amode_rdonly)

! read version, matrix name and matrix type
      CALL mp_file_read_at_all(thefile, BOF, version)

      IF (version /= version_v_1_0) &
         DBCSR_WARN("Trying to read an unknown version of the matrix data file. Good luck!")

      CALL mp_file_read_at_all(thefile, BOF + version_len*mpi_character_size, matrix_name_v_1_0)
      matrix_name = matrix_name_v_1_0

      CALL mp_file_read_at_all(thefile, BOF + (version_len + default_string_length)*mpi_character_size, matrix_type)
! read 4 integer values form sub-header1
      CALL mp_file_read_at_all(thefile, BOF + char_count*mpi_character_size, values)
      size_of_pgrid = values(1)
      data_type = values(2)
      nblkrows_total = values(3)
      nblkcols_total = values(4)
! read 2 vectors, row_blk_size and col_blk_size, from sub-header1
      globalinfo_size = nblkrows_total + nblkcols_total
      ALLOCATE (ginfo_vec(globalinfo_size))
      CALL mp_file_read_at_all(thefile, BOF + char_count*mpi_character_size + 4*mpi_integer_size, ginfo_vec)
      row_blk_size => ginfo_vec(1:nblkrows_total)
      col_blk_size => ginfo_vec(nblkrows_total + 1:globalinfo_size)

! compute the offsets where sub-header2 and sub-header3 start
      subh2_start = (4 + globalinfo_size)*mpi_integer_size + char_count*mpi_character_size
      subh3_start = subh2_start + 2*size_of_pgrid*mpi_integer_size

! compute the offsets in sub-header2 and read 2 integers nblocks, data_area_size
      ! number of data chunks from sub-header 2 and 3 to be read by every node rounded up
      ! to the next integer to make it even for all the nodes in the specified mpi group
      share_size = CEILING(REAL(size_of_pgrid, KIND=dp)/group_list_size)

      ALLOCATE (subh2_offsets(share_size))
      subh2_offsets = BOF
      DO i = 1, share_size
         offset = subh2_start + mpi_integer_size*2*(worker_id + (i - 1)*group_list_size)
         IF (offset .GE. subh3_start) EXIT
         subh2_offsets(i) = offset
      END DO

      ALLOCATE (val_data(3, share_size))
      val_data(:, :) = 0
      DO i = 1, share_size
         CALL mp_file_read_at_all(thefile, subh2_offsets(i), values, msglen=2)
         nblocks = values(1)
         data_area_size = values(2)
         IF (subh2_offsets(i) .EQ. 0) EXIT
         val_data(1, i) = nblocks
         IF (data_area_size >= HUGE(val_data(2, i))) &
            DBCSR_ABORT("Data area too large, fix code.")
         val_data(2, i) = INT(data_area_size)
         val_data(3, i) = worker_id + (i - 1)*group_list_size + 1 ! order
         ! order = indices of an array of length size_of_pgrid to be accessed by the current node
      END DO
      nblks = SUM(val_data(1, :))
      darea_size = SUM(val_data(2, :))
      proc_nblks => val_data(1, :) ! to be passed to dbcsr_datablock_redistribute
      proc_darea_sizes => val_data(2, :) ! to be passed to dbcsr_datablock_redistribute

! compute the offsets in sub-header3 and read 3 vectors row_p, col_i, blk_p
      ! actual number of chunks to be read by the current node
      job_count = COUNT(val_data(3, :) .NE. 0)
      CALL mp_file_get_size(thefile, file_size)

      ALLOCATE (linfo_lens(size_of_pgrid))
      ALLOCATE (subh3_disps(size_of_pgrid))
      ALLOCATE (subh3_offsets(size_of_pgrid))
      linfo_lens = 0; subh3_disps = 0
      DO i = 1, size_of_pgrid
         DO j = 1, share_size
            order = val_data(3, j)
            IF (i .EQ. order) linfo_lens(order) = &
               1 + nblkrows_total + 2*val_data(1, j)
         END DO
      END DO
      CALL mp_sum(linfo_lens, group_id)
      CALL cumsum_l(INT(linfo_lens, kind=file_offset), subh3_disps)
      subh3_disps(:) = CSHIFT(subh3_disps, shift=-1)
      subh3_disps(1) = BOF
      subh3_offsets(:) = subh3_start + subh3_disps*mpi_integer_size

      sum_nblks = INT(nblks, kind=file_offset)
      CALL mp_sum(sum_nblks, group_id)
      subh3_length = size_of_pgrid*INT(1 + nblkrows_total, KIND=file_offset) + 2*sum_nblks

      linfo_length = nblkrows_total + 1 + 2*MAXVAL(val_data(1, :))

      ALLOCATE (linfo_vec(linfo_length))
      ALLOCATE (rowp((nblkrows_total + 1)*job_count))
      ALLOCATE (coli(nblks))
      ALLOCATE (blkp(nblks))
      DO i = 1, share_size
         order = val_data(3, i)
         cur_blks = val_data(1, i)
         IF (order .EQ. 0) THEN
            localinfo_offset = file_size
            localinfo_length = 0
         ELSE
            localinfo_offset = subh3_offsets(order)
            localinfo_length = linfo_lens(order)
         END IF
         CALL mp_file_read_at_all(thefile, localinfo_offset, linfo_vec, msglen=localinfo_length)
         IF (localinfo_length .EQ. 0) EXIT

         rowp((i - 1)*(nblkrows_total + 1) + 1:i*(nblkrows_total + 1)) = linfo_vec(1:nblkrows_total + 1)
         start_index = SUM(val_data(1, 1:i - 1)) + 1
         end_index = SUM(val_data(1, 1:i))
         coli(start_index:end_index) = &
            linfo_vec(nblkrows_total + 2:cur_blks + nblkrows_total + 1)
         blkp(start_index:end_index) = &
            linfo_vec(cur_blks + nblkrows_total + 2:2*cur_blks + nblkrows_total + 1)
      END DO
      row_p => rowp
      col_i => coli
      blk_p => blkp

! compute the offsets and read block data
      ALLOCATE (bdata_lens(size_of_pgrid))
      ALLOCATE (bdata_disps(size_of_pgrid))
      ALLOCATE (bdata_offsets(size_of_pgrid))
      bdata_lens = 0
      DO i = 1, size_of_pgrid
         DO j = 1, share_size
            order = val_data(3, j)
            IF (i .EQ. order) bdata_lens(order) = val_data(2, j)
         END DO
      END DO
      CALL mp_sum(bdata_lens, group_id)
      CALL cumsum_l(INT(bdata_lens, kind=file_offset), bdata_disps)
      bdata_disps(:) = CSHIFT(bdata_disps, shift=-1)
      bdata_disps(1) = BOF

      bdata_start = subh3_start + subh3_length*mpi_integer_size
      SELECT CASE (data_type)
      CASE (dbcsr_type_real_4)
         type_size = real_4_size
      CASE (dbcsr_type_real_8)
         type_size = real_8_size
      CASE (dbcsr_type_complex_4)
         type_size = 2*real_4_size
      CASE (dbcsr_type_complex_8)
         type_size = 2*real_8_size
      END SELECT
      bdata_offsets(:) = bdata_start + bdata_disps*type_size

      SELECT CASE (data_type)
      CASE (dbcsr_type_real_4)
         ALLOCATE (rsp(darea_size))
         DO i = 1, share_size
            order = val_data(3, i)
            ! use dummy one-sized data array as buffer in place of empty array
            ! when nothing is supposed to be read (order = 0)
            IF (order .EQ. 0) THEN
               blockdata_offset = file_size
               CALL mp_file_read_at_all(thefile, blockdata_offset, rsp_dummy)
            ELSE
               start_index = SUM(val_data(2, 1:i - 1)) + 1
               end_index = SUM(val_data(2, 1:i))
               blockdata_length = bdata_lens(order)
               blockdata_offset = bdata_offsets(order)
               CALL mp_file_read_at_all(thefile, blockdata_offset, rsp(start_index:end_index), &
                                        msglen=blockdata_length)
            END IF
         END DO
      CASE (dbcsr_type_real_8)
         ALLOCATE (rdp(darea_size))
         DO i = 1, share_size
            order = val_data(3, i)
            IF (order .EQ. 0) THEN
               blockdata_offset = file_size
               CALL mp_file_read_at_all(thefile, blockdata_offset, rdp_dummy)
            ELSE
               start_index = SUM(val_data(2, 1:i - 1)) + 1
               end_index = SUM(val_data(2, 1:i))
               blockdata_length = bdata_lens(order)
               blockdata_offset = bdata_offsets(order)
               CALL mp_file_read_at_all(thefile, blockdata_offset, rdp(start_index:end_index), &
                                        msglen=blockdata_length)
            END IF
         END DO
      CASE (dbcsr_type_complex_4)
         ALLOCATE (csp(darea_size))
         DO i = 1, share_size
            order = val_data(3, i)
            IF (order .EQ. 0) THEN
               blockdata_offset = file_size
               CALL mp_file_read_at_all(thefile, blockdata_offset, csp_dummy)
            ELSE
               start_index = SUM(val_data(2, 1:i - 1)) + 1
               end_index = SUM(val_data(2, 1:i))
               blockdata_length = bdata_lens(order)
               blockdata_offset = bdata_offsets(order)
               CALL mp_file_read_at_all(thefile, blockdata_offset, csp(start_index:end_index), &
                                        msglen=blockdata_length)
            END IF
         END DO
      CASE (dbcsr_type_complex_8)
         ALLOCATE (cdp(darea_size))
         DO i = 1, share_size
            order = val_data(3, i)
            IF (order .EQ. 0) THEN
               blockdata_offset = file_size
               CALL mp_file_read_at_all(thefile, blockdata_offset, cdp_dummy)
            ELSE
               start_index = SUM(val_data(2, 1:i - 1)) + 1
               end_index = SUM(val_data(2, 1:i))
               blockdata_length = bdata_lens(order)
               blockdata_offset = bdata_offsets(order)
               CALL mp_file_read_at_all(thefile, blockdata_offset, cdp(start_index:end_index), &
                                        msglen=blockdata_length)
            END IF
         END DO
      END SELECT
      CALL dbcsr_data_init(dblk)
      CALL dbcsr_data_new(dblk, data_type)
      IF (ALLOCATED(rdp)) dblk%d%r_dp => rdp
      IF (ALLOCATED(rsp)) dblk%d%r_sp => rsp
      IF (ALLOCATED(cdp)) dblk%d%c_dp => cdp
      IF (ALLOCATED(csp)) dblk%d%c_sp => csp

      CALL mp_file_close(thefile)

      CALL dbcsr_create(matrix_new, matrix_name, distribution, matrix_type, &
                        row_blk_size, col_blk_size, nze=darea_size, &
                        data_type=data_type)
      CALL dbcsr_datablock_redistribute(dblk, row_p, col_i, blk_p, proc_nblks, proc_darea_sizes, matrix_new)

      DEALLOCATE (subh2_offsets, subh3_offsets, bdata_offsets)
      DEALLOCATE (subh3_disps, bdata_disps)
      DEALLOCATE (linfo_lens, bdata_lens)
      DEALLOCATE (val_data, ginfo_vec, linfo_vec)
      DEALLOCATE (rowp, coli, blkp)
      IF (ALLOCATED(rdp)) DEALLOCATE (rdp)
      IF (ALLOCATED(rsp)) DEALLOCATE (rsp)
      IF (ALLOCATED(cdp)) DEALLOCATE (cdp)
      IF (ALLOCATED(csp)) DEALLOCATE (csp)
      CALL dbcsr_data_clear_pointer(dblk)
      DEALLOCATE (dblk%d)

      CALL timestop(handle)
   CONTAINS
      SUBROUTINE cumsum_l(arr, cumsum)
         INTEGER(kind=file_offset), DIMENSION(:), &
            INTENT(IN)                                      :: arr
         INTEGER(kind=file_offset), DIMENSION(SIZE(arr)), &
            INTENT(OUT)                                     :: cumsum

         INTEGER                                            :: i

         cumsum(1) = arr(1)
         DO i = 2, SIZE(arr)
            cumsum(i) = cumsum(i - 1) + arr(i)
         END DO
      END SUBROUTINE cumsum_l

   END SUBROUTINE dbcsr_binary_read

   SUBROUTINE dbcsr_print_block_sum(matrix, unit_nr)
      !! Prints the sum of the elements for each block

      TYPE(dbcsr_type), INTENT(IN)                       :: matrix
         !! matrix
      INTEGER, INTENT(IN), OPTIONAL                      :: unit_nr

      CHARACTER(len=*), PARAMETER :: routineN = 'dbcsr_print_block_sum'

      COMPLEX(KIND=real_4)                               :: blk_sum_c_sp
      COMPLEX(KIND=real_4), DIMENSION(:), POINTER        :: c_sp
      COMPLEX(KIND=real_8)                               :: blk_sum_c_dp
      COMPLEX(KIND=real_8), DIMENSION(:), POINTER        :: c_dp
      INTEGER                                            :: bc, blk, blk_p, br, handle, iunit, m, &
                                                            mn, n
      INTEGER, DIMENSION(:), POINTER                     :: col_blk_offset, col_blk_size, &
                                                            row_blk_offset, row_blk_size
      REAL(KIND=real_4)                                  :: blk_sum_r_sp
      REAL(KIND=real_4), DIMENSION(:), POINTER           :: r_sp
      REAL(KIND=real_8)                                  :: blk_sum_r_dp
      REAL(KIND=real_8), DIMENSION(:), POINTER           :: r_dp

!   ---------------------------------------------------------------------------

      CALL timeset(routineN, handle)
      IF (.NOT. dbcsr_valid_index(matrix)) &
         DBCSR_WARN("Can not print invalid matrix.")

      iunit = default_output_unit
      IF (PRESENT(unit_nr)) iunit = unit_nr

      IF (iunit > 0) THEN

         SELECT CASE (matrix%data_type)
         CASE (dbcsr_type_real_8)
            CALL dbcsr_get_data(matrix%data_area, r_dp)
         CASE (dbcsr_type_real_4)
            CALL dbcsr_get_data(matrix%data_area, r_sp)
         CASE (dbcsr_type_complex_8)
            CALL dbcsr_get_data(matrix%data_area, c_dp)
         CASE (dbcsr_type_complex_4)
            CALL dbcsr_get_data(matrix%data_area, c_sp)
         END SELECT
         row_blk_size => array_data(matrix%row_blk_size)
         col_blk_size => array_data(matrix%col_blk_size)
         row_blk_offset => array_data(matrix%row_blk_offset)
         col_blk_offset => array_data(matrix%col_blk_offset)

         IF (matrix%nblks .GT. 0) THEN
            DO br = 1, matrix%nblkrows_total
               m = row_blk_size(br)
               DO blk = matrix%row_p(br) + 1, matrix%row_p(br + 1)
                  bc = matrix%col_i(blk)
                  n = col_blk_size(bc)
                  mn = m*n
                  blk_p = ABS(matrix%blk_p(blk))
                  block_exists: IF (blk_p .NE. 0) THEN
                     IF (mn .GT. 0) THEN
                        SELECT CASE (matrix%data_type)
                        CASE (dbcsr_type_real_8)
                           blk_sum_r_dp = SUM(r_dp(blk_p:blk_p + mn - 1))
                           WRITE (iunit, '(I6,I6,ES18.9)') &
                              br, bc, blk_sum_r_dp
                        CASE (dbcsr_type_real_4)
                           blk_sum_r_sp = SUM(r_sp(blk_p:blk_p + mn - 1))
                           WRITE (iunit, '(I6,I6,ES18.9)') &
                              br, bc, blk_sum_r_sp
                        CASE (dbcsr_type_complex_8)
                           blk_sum_c_dp = SUM(c_dp(blk_p:blk_p + mn - 1))
                           WRITE (iunit, '(I6,I6,ES18.9," I*",ES18.9)') &
                              br, bc, REAL(blk_sum_c_dp), AIMAG(blk_sum_c_dp)
                        CASE (dbcsr_type_complex_4)
                           blk_sum_c_sp = SUM(c_sp(blk_p:blk_p + mn - 1))
                           WRITE (iunit, '(I6,I6,ES18.9," I*",ES18.9)') &
                              br, bc, REAL(blk_sum_c_sp), AIMAG(blk_sum_c_sp)
                        END SELECT
                     ELSE
                        blk_sum_r_dp = 0.0_dp
                        WRITE (iunit, '(I6,I6,ES18.9)') &
                           br, bc, blk_sum_r_dp
                     END IF
                  END IF block_exists
               END DO
            END DO
         END IF

      END IF ! unit > 0

      CALL timestop(handle)

   END SUBROUTINE dbcsr_print_block_sum

END MODULE dbcsr_io
